﻿using IdentityManagement.Localization;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
using Xms.Abp.Application.Services;

namespace IdentityManagement
{
    public class IdentityUserAppService : XmsCrudAppService<IdentityUser, IdentityUserResult,
            Guid,
            IdentityUserPagedParameter,
            IdentityUserCreateParameter,
            IdentityUserUpdateParameter>, IIdentityUserAppService
    {
        public IdentityUserAppService(
            IdentityUserManager userManager,
            IIdentityUserRepository userRepository,
            IIdentityRoleRepository roleRepository,
            IOptions<IdentityOptions> identityOptions) : base(userRepository)
        {
            UserManager = userManager;
            UserRepository = userRepository;
            RoleRepository = roleRepository;
            IdentityOptions = identityOptions;

            //ObjectMapperContext = typeof(IdentityManagementApplicationModule);
            LocalizationResource = typeof(IdentityResource);
        }

        protected IdentityUserManager UserManager { get; }
        protected IIdentityUserRepository UserRepository { get; }
        protected IIdentityRoleRepository RoleRepository { get; }
        protected IOptions<IdentityOptions> IdentityOptions { get; }

        //TODO: [Authorize(IdentityPermissions.Users.Default)] should go the IdentityUserAppService class.
        public override async Task<IdentityUserResult> GetAsync(Guid id)
        {
            return ObjectMapper.MapTo<IdentityUserResult>(await UserManager.GetByIdAsync(id));
        }

        protected override async Task<IQueryable<IdentityUser>> CreateFilteredQueryAsync(IdentityUserPagedParameter input)
        {
            var query = await base.CreateFilteredQueryAsync(input);
            query = query.WhereIf(!input.Filter.IsNullOrWhiteSpace(), u =>
                        u.UserName.Contains(input.Filter) ||
                        u.Email.Contains(input.Filter) ||
                        (u.Name != null && u.Name.Contains(input.Filter)) ||
                        (u.Surname != null && u.Surname.Contains(input.Filter)) ||
                        (u.PhoneNumber != null && u.PhoneNumber.Contains(input.Filter)));
            return query;
        }

        public virtual async Task<List<IdentityRoleResult>> GetRolesAsync(Guid id)
        {
            //TODO: Should also include roles of the related OUs.

            var roles = await UserRepository.GetRolesAsync(id);

            return ObjectMapper.MapTo<IdentityRoleResult>(roles);
        }

        public virtual async Task<List<IdentityRoleResult>> GetAssignableRolesAsync()
        {
            var list = await RoleRepository.GetListAsync();
            return ObjectMapper.MapTo<IdentityRoleResult>(list);
        }

        public override async Task<IdentityUserResult> CreateAsync(IdentityUserCreateParameter input)
        {
            await CheckCreatePolicyAsync();

            await IdentityOptions.SetAsync();

            var user = new IdentityUser(
                GuidGenerator.Create(),
                input.UserName,
                input.PhoneNumber,
                input.Email,
                CurrentTenant.Id
            );

            input.MapExtraPropertiesTo(user);

            (await UserManager.CreateAsync(user, input.Password)).CheckErrors();
            await UpdateUserByInput(user, input);
            (await UserManager.UpdateAsync(user)).CheckErrors();

            await CurrentUnitOfWork.SaveChangesAsync();

            return ObjectMapper.MapTo<IdentityUserResult>(user);
        }

        public override async Task<IdentityUserResult> UpdateAsync(Guid id, IdentityUserUpdateParameter input)
        {
            await CheckUpdatePolicyAsync();

            await IdentityOptions.SetAsync();

            var user = await UserManager.GetByIdAsync(id);

            user.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp);

            (await UserManager.SetUserNameAsync(user, input.UserName)).CheckErrors();

            await UpdateUserByInput(user, input);
            input.MapExtraPropertiesTo(user);

            (await UserManager.UpdateAsync(user)).CheckErrors();

            if (!input.Password.IsNullOrEmpty())
            {
                (await UserManager.RemovePasswordAsync(user)).CheckErrors();
                (await UserManager.AddPasswordAsync(user, input.Password)).CheckErrors();
            }

            await CurrentUnitOfWork.SaveChangesAsync();

            return ObjectMapper.Map<IdentityUser, IdentityUserResult>(user);
        }

        public override async Task DeleteAsync(Guid id)
        {
            await CheckDeletePolicyAsync();
            if (CurrentUser.Id == id)
            {
                throw new BusinessException(code: IdentityErrorCodes.UserSelfDeletion);
            }

            var user = await UserManager.FindByIdAsync(id.ToString());
            if (user == null)
            {
                return;
            }

            (await UserManager.DeleteAsync(user)).CheckErrors();
        }

        public virtual async Task UpdateRolesAsync(Guid id, IdentityUserUpdateRolesParameter input)
        {
            var user = await UserManager.GetByIdAsync(id);
            (await UserManager.SetRolesAsync(user, input.RoleNames)).CheckErrors();
            await UserRepository.UpdateAsync(user);
        }

        public virtual async Task<IdentityUserResult> FindByUsernameAsync(string userName)
        {
            return ObjectMapper.Map<IdentityUser, IdentityUserResult>(
                await UserManager.FindByNameAsync(userName)
            );
        }

        public virtual async Task<IdentityUserResult> FindByEmailAsync(string email)
        {
            return ObjectMapper.Map<IdentityUser, IdentityUserResult>(
                await UserManager.FindByEmailAsync(email)
            );
        }

        public virtual async Task<IdentityUserResult> FindByPhoneNumberAsync(string email)
        {
            return ObjectMapper.Map<IdentityUser, IdentityUserResult>(
                await UserManager.FindByPhoneNumberAsync(email)
            );
        }

        protected virtual async Task UpdateUserByInput(IdentityUser user, IdentityUserCreateOrUpdateParameterBase input)
        {
            if (!string.Equals(user.Email, input.Email, StringComparison.InvariantCultureIgnoreCase))
            {
                (await UserManager.SetEmailAsync(user, input.Email)).CheckErrors();
            }

            if (!string.Equals(user.PhoneNumber, input.PhoneNumber, StringComparison.InvariantCultureIgnoreCase))
            {
                (await UserManager.SetPhoneNumberAsync(user, input.PhoneNumber)).CheckErrors();
            }

            (await UserManager.SetLockoutEnabledAsync(user, input.LockoutEnabled)).CheckErrors();

            user.Name = input.Name;
            user.Surname = input.Surname;
            (await UserManager.UpdateAsync(user)).CheckErrors();
            user.SetIsActive(input.IsActive);
            if (input.RoleNames != null)
            {
                (await UserManager.SetRolesAsync(user, input.RoleNames)).CheckErrors();
            }
        }
    }
}
